/**
 * 
 */
package simulation;

import java.util.Random;

import common.Neighbors;

/**
 * @author vladimir
 * 
 */
public class LatticeClock {
	public int nSites = -1;

	Random rand = new Random();

	int[][][][] neighbors3 = null;
	int[][][][] neighbors2 = null;
	int[][] neighbors1 = null;
	public int coordNumber = 0;
	int dimension = -1;
	double J = 0;
	int L = -1;
	double T = -1;
	public double[] tCos;
	public double[] tSin;
	public int q = -1;
	public int[][] lattice2;

	public LatticeClock(int[][] tneighbors, double J, int L, int q, double T) {
		this.L = L;
		this.J = J;
		this.q = q;
		this.T = T;
		this.dimension = tneighbors[0].length;
		this.coordNumber = tneighbors.length;
		this.lattice2 = new int[this.L][this.L];
		this.nSites = (int) Math.pow(this.L, this.dimension);

		// fill lattice
		for (int x = 0; x < this.L; x++) {
			for (int y = 0; y < this.L; y++) {
				this.lattice2[x][y] = this.rand.nextInt(this.q);
			}
		}
		Neighbors NB = new Neighbors(tneighbors, this.L, this.L);
		this.neighbors2 = NB.neighbors2;
		// fill tSin and tCos arrays
		this.tSin = new double[2 * this.q - 1];
		this.tCos = new double[2 * this.q - 1];
		double tq = 2 * Math.PI / ((double) this.q);

		for (int i = 0; i < this.tSin.length; i++) {
			this.tSin[i] = Math.sin(tq * (i - (this.q - 1)));
			this.tCos[i] = Math.cos(tq * (i - (this.q - 1)));

		}
	}

	public double Energy() {
		double result = 0;
		for (int x = 0; x < this.L; x++) {
			for (int y = 0; y < this.L; y++) {
				int ni = this.lattice2[x][y];
				for (int tn = 0; tn < this.coordNumber; tn++) {
					int nj = neighbor_value(x, y, tn);
					result -= Cos(ni - nj);
				}
			}
		}
		return this.J * result / 2 / this.nSites;
	}

	public double Cos(int i) {
		return this.tCos[i + this.q - 1];
	}

	public double Sin(int i) {
		return this.tSin[i + this.q - 1];
	}

	public int newSiteValue(int oldSite) {
		int result = rand.nextInt(this.q);
		while (result == oldSite) {
			result = this.rand.nextInt(this.q);
		}
		return result;
	}

	/* function tries flip spin in lattice */
	public void update() {
		for (int x = 0; x < this.L; x++) {
			for (int y = 0; y < this.L; y++) {
				int tx = rand.nextInt(this.L);
				int ty = rand.nextInt(this.L);
				int ni = this.lattice2[tx][ty];

				int ni_new = newSiteValue(ni);
				double tdE = dE(tx, ty, ni_new);
				float p = this.rand.nextFloat();
				if (p < (1 / (1 + Math.exp(tdE / this.T)))) {
					this.lattice2[tx][ty] = ni_new;
				}
			}
		}
	}

	// change in Energy when we changevalue at x,y to newValue
	public double dE(int ttx, int tty, int newValue) {
		int ni = this.lattice2[ttx][tty];
		double result = 0;
		for (int tn = 0; tn < this.coordNumber; tn++) {
			int nj = neighbor_value(ttx, tty, tn);
			result -= (this.Cos(newValue - nj) - this.Cos(ni - nj));
		}
		return result * this.J;

	}

	public int neighbor_value(int x, int y, int tn) {
		int nx = this.neighbors2[x][y][tn][0];
		int ny = this.neighbors2[x][y][tn][1];
		return this.lattice2[nx][ny];
	}

	public String print() {
		String result = "";
		for (int x = 0; x < this.L; x++) {
			for (int y = 0; y < this.L; y++) {
				result += Integer.toString(this.lattice2[x][y]);
			}
			result += "\n";
		}
		return result;
	}

	public double M() {
		double result = 0;
		result += Math.sqrt(Math.pow(Mx(), 2) + Math.pow(My(), 2));
		return result / Math.pow(this.L, this.dimension);
	}

	public double Mx() {
		double result = 0;
		for (int x = 0; x < this.L; x++) {
			for (int y = 0; y < this.L; y++) {
				int ni = this.lattice2[x][y];
				result += Cos(ni);
			}
		}
		return result;
	}

	public double My() {
		double result = 0;
		for (int x = 0; x < this.L; x++) {
			for (int y = 0; y < this.L; y++) {
				int ni = this.lattice2[x][y];
				result += Sin(ni);
			}
		}
		return result;
	}
}
